/*
 * 在CPU上模拟GPU的操作.
 */

#include "OnCPU.h"
#include "omp.h"

static int IPX[][2] = {{-1,0},{-1,-1},{-1,1},{-2,0},{-2,-1},{-2,1},{0,0}};
static int IPY[][2] = {{0,-1},{-1,-1},{1,-1},{0,-2},{-1,-2},{1,-2},{0,0}};

static color *refc, *inpc;
static ACCM *preline, *curline;
static int buf_size;

static color* copyImageToDevice(Image *image);
static short calDistance(color *c1, color *c2);
static ACCM * get(ACCM *, int, int, int);
static int ccudaMalloc(void**, int);
static int ccudaMemcpy(void*, void*, int);
static int ccudaMemset(void*, int, int);
static int ccudaFree(void*);

void accms(int, int, int, int, int);

void cpu_initializeDeviceBuf(int s){
	refc = copyImageToDevice(&ref);
	inpc = copyImageToDevice(&inp);
	buf_size = s;
	ccudaMalloc((void**)&preline, sizeof(ACCM) * inpwidth * inpheight * buf_size);
	ccudaMalloc((void**)&curline, sizeof(ACCM) * inpwidth * inpheight * buf_size);
}

//2.
void cpu_initCurStart(ACCM *value){
	ccudaMemcpy(&curline[0], value, sizei);
}

//3.
void cpu_accmcalc(int m, int n, int tsk) {
	 #pragma omp parallel
         {
		int tid = omp_get_thread_num();
		int maxtid = omp_get_num_threads();
		int i, j;
		for(i = tid; i < inp.width; i += maxtid){
			for(j = 0; j < inp.height; ++j){
				accms(m, n, i, j, tsk);
			}		
		}
         }
}

//4.
void cpu_copyResult(ACCM *buf, int ri){
	ccudaMemcpy(buf, &curline[ri*inpwidth*inpheight], sizei);
}

//6.
void cpu_switchPreAndCurLine(){
	ACCM* tmp = preline;
	preline = curline;
	curline = tmp;
	ccudaMemset(curline, 0, sizeof(ACCM) * buf_size * inpwidth * inpheight);
}

void cpu_cudaFinalize(){
	ccudaFree(refc);
	ccudaFree(inpc);
	ccudaFree(preline);
	ccudaFree(curline);
}

void accms(int m, int n, int i, int j, int tsk){
	int l, x, y;
	int min, path;

	int st = 0;
	if(rank == 0){
		st = 1;
	}
	if (m > st) {
		path = -1;
		min = INF;
		for (l = 0; l < 7; ++l) {
			x = IPX[l][0] + i;
			y = IPX[l][1] + j;
			if (x < 0 || y < 0 || y >= inpheight) {
				continue;
			}
			ACCM *accm = get(curline, m-1, x, y);
			int dx = accm->dxx + accm->dyy + accm->dxy;
			if (min > dx) {
				if (l == 6 && accm->x0 >= LIMIT) {
					continue;
				}
				min = dx;
				path = l;
			}
		}
		ACCM *accm = get(curline, m, i, j);
		if (path < 0) {
			accm->dxx = ERR;
			accm->dxy = ERR;
		} else {
			x = IPX[path][0] + i;
			y = IPX[path][1] + j;
			ACCM *accm1 = get(curline, m-1, x, y);
			accm->dxx = calDistance(&refc[(tsk + m)*ref.height+n], &inpc[i*inpheight+j]) + accm1->dxx;
			accm->dxy = accm1->dxy + accm1->dyy;
			if (path == 6) {
				accm->x0 = accm1->x0 + 1;
			}
		}
	} else {
		ACCM *accm = get(curline, m, i, j);
		accm->dxx = calDistance(&refc[(tsk + m)*ref.height+n], &inpc[i*inpheight+j]);
	}
	if (n > 0) {
		path = -1;
		min = INF;
		for (l = 0; l < 7; ++l) {
			x = IPY[l][0] + i;
			y = IPY[l][1] + j;
			if (y < 0 || x < 0 || x >= inpwidth) {
				continue;
			}
			ACCM *accm = get(preline, m, x, y);
			int dy = accm->dxx + accm->dyy + accm->dyx;
			if (min > dy) {
				if (l == 6 && accm->y0 >= LIMIT) {
					continue;
				}
				min = dy;
				path = l;
			}
		}
		ACCM *accm = get(curline, m, i, j);
		if (path < 0) {
			accm->dyx = ERR;
			accm->dyy = ERR;
		} else {
			x = IPY[path][0] + i;
			y = IPY[path][1] + j;
			ACCM *accm1 = get(preline, m, x, y);
			accm->dyy = calDistance(&refc[(tsk + m)*ref.height+n], &inpc[i*inpheight+j]) + accm1->dyy;
			accm->dyx = accm1->dyx + accm1->dxx;
			if (path == 6) {
				accm->y0 = accm1->y0 + 1;
			}
		}
	}else {
		color *c = &refc[(tsk + m)*ref.height+n];
		ACCM *accm = get(curline, m, i, j);
		accm->dyy = calDistance(c, &inpc[i*inpheight+j]);
	}
}

static color* copyImageToDevice(Image *image){
	color* dist;
	int k;
	ccudaMalloc((void**)&dist, sizeof(color) * image->width * image->height);
	for(k = 0; k < image->width; ++k){
		ccudaMemcpy(&dist[k * image->height], image->data[k],
				image->height * sizeof(color));
	}
	return dist;
}

static short calDistance(color *c1, color *c2) {
	if(c1->b == 255 && c1->r == 0 && c1->g == 0){
		return DMAX;
	}else{
		return (abs(c1->r - c2->r) + abs(c1->g - c2->g) + abs(c1->b - c2->b));
	}
}

static ACCM * get(ACCM *cur, int m, int i, int j){
	return &cur[(m * inpwidth + i) * inpheight + j];
}

static int ccudaMalloc(void** p, int size){
	*p = malloc(size);
	return 0;
}
static int ccudaMemcpy(void *dest, void *src, int size){
	memcpy(dest, src, size);
	return 0;
}
static int ccudaMemset(void *p, int v, int size){
	memset(p, v, size);
	return 0;
}
static int ccudaFree(void *p){
	free(p);
	return 0;
}
